package nts

import (
	"fmt"
	"git.oschina.net/go-pkg/nmap"
	nm "git.oschina.net/go-pkg/nmap/models"
	m "git.oschina.net/go-pkg/nts/models"
	"github.com/inconshreveable/log15"
	"net"
)

var (
	logger   = log15.Root()
	nmapInst = nmap.New()
	IsDebug  = false
)

const (
	default_net_chan_size    int = 10
	default_net_os_size      int = 5
	default_net_snmpsvc_size int = 5
)

type DevHandler func(*m.Dev)

type Scanner struct {
	netC chan string
	osC  chan *m.Dev
	// snmpSvcC  chan *m.Dev
	nmapInstC chan nmap.Nmap

	ScanResultC chan int

	netCLen int
	osCLen  int

	handler *nativeHandler

	devHandlers []DevHandler
	osHandlers  []DevHandler
}

var scannerInst = newScanner()

func newScanner() *Scanner {
	nmapC := make(chan nmap.Nmap, 5)
	for i := 0; i < cap(nmapC); i++ {
		nmapC <- nmapInst
	}
	scanner := &Scanner{
		netC: make(chan string, default_net_chan_size),
		osC:  make(chan *m.Dev, default_net_os_size),
		// snmpSvcC:    make(chan *m.Dev, default_net_snmpsvc_size),
		ScanResultC: make(chan int),
		handler:     &nativeHandler{},
		nmapInstC:   nmapC,
	}
	scanner.Run()
	return scanner
}

func (s *Scanner) Run() {
	go s.scanNet()
	go s.scanOs()
	// go s.scanSnmpSvc()
}

func debug(format string, v ...interface{}) {
	if IsDebug {
		var message string
		if len(v) > 0 {
			message = fmt.Sprintf(format, v...)
		} else {
			message = format
		}

		logger.Debug(message)
	}
}

func getNetwork(network string) string {
	_, net, _ := net.ParseCIDR(network)
	return net.String()
}

func AddDevDetectHandler(handler DevHandler) {
	scannerInst.AddDevDetectHandler(handler)
}

func AddOsDetectHandler(handler DevHandler) {
	scannerInst.AddOsDetectHandler(handler)
}

func (s *Scanner) AddDevDetectHandler(handler DevHandler) {
	s.devHandlers = append(s.devHandlers, handler)
}

func (s *Scanner) AddOsDetectHandler(handler DevHandler) {
	s.osHandlers = append(s.osHandlers, handler)
}

func (s *Scanner) AddNet(network string) {
	s.netC <- getNetwork(network)
	s.netCLen++
}

/*
func (s *Scanner) AddDev(addr string) {
	s.osC <- addr
}*/

func handle(handlers []DevHandler, dev *m.Dev) {
	for _, handler := range handlers {
		handler(dev)
	}
}

func (s *Scanner) scanNet() {
	for {
		network := <-s.netC
		go func() {
			nmapInst := <-s.nmapInstC
			debug("scan network %s", network)
			nmapRun, err := snmpDectect(nmapInst, []string{network})
			s.netCLen--
			s.nmapInstC <- nmapInst
			if nil == err {
				devs, _ := s.handler.OnNetResult(network, nmapRun)
				for _, dev := range devs {
					dev.IPNet = network
					handle(s.devHandlers, dev)
					s.osC <- dev
					s.osCLen++
				}
			}
		}()
	}
}

func (s *Scanner) scanOs() {
	for {
		dev := <-s.osC
		//you can define a size, and fetch size of devs or rest of devs less than the size, then detect their os together
		go func() {
			nmapInst := <-s.nmapInstC
			debug("detect os %s", dev.IP)
			nmapRun, err := osDectect(nmapInst, []string{dev.IP})
			s.osCLen--
			s.nmapInstC <- nmapInst
			if nil == err {
				s.handler.OnOsResult(dev, nmapRun)
				handle(s.osHandlers, dev)
				debug("dev %s os %s %s snmp %t", dev.IP, dev.OsFamily, dev.OsName, dev.IsSnmpRunning)
				debug("dev os len: %d", s.osCLen)
				// s.snmpSvcC <- dev
			}
		}()
	}
}

/*func (s *Scanner) scanSnmpSvc() {
	for {
		dev := <-s.snmpSvcC
		go func() {
			nmapInst := <-s.nmapInstC
			debug("detect snmp %s", dev.IP)
			if nmapRun, err := snmpDectect(nmapInst, []string{dev.IP}); nil == err {
				s.nmapInstC <- nmapInst
				s.handler.OnSnmpSvcResult(dev, nmapRun)
				debug("dev %s snmp enabled %t", dev.IP, dev.SnmpSvcRunning)
				s.snmpSvcCLen--
				debug("dev snmp len: %d", s.snmpSvcCLen)
			} else {
				s.nmapInstC <- nmapInst
				debug("%s", err.Error())
			}
		}()
	}
}*/

func (s *Scanner) IsRunning() bool {
	return s.osCLen > 0 || s.osCLen > 0
}

func osDectect(nmapInst nmap.Nmap, targets []string) (*nm.NmapRun, error) {
	return nmapInst.Scan(targets, "-O", "-F", "-n", "-T4")
}

func snmpDectect(nmapInst nmap.Nmap, targets []string) (*nm.NmapRun, error) {
	return nmapInst.Scan(targets, "-sU", "-p161", "-T4")
}

func traceRoute(nmapInst nmap.Nmap, targets []string) (*nm.NmapRun, error) {
	return nmapInst.Scan(targets, "-sn", "--traceroute")
}

func AddNet(network string) {
	scannerInst.AddNet(network)
}

func GetScanResultChan() <-chan int {
	return scannerInst.ScanResultC
}

func IsScanning() bool {
	return scannerInst.IsRunning()
}
